﻿using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Drawing;
using System.IO.Pipes;
using System.Net;
using System.Net.Sockets;
using System.Reflection.Metadata.Ecma335;
using System.Text;
using System.Threading.Channels;
using System.Threading.Tasks;
using System.Xml.Linq;
using UMC.Data;

namespace UMC.Net
{

    public abstract class NetBridge
    {

        async void Receive(ChannelReader<Tuple<long, int>> reader)
        {
            var buffer = ArrayPool<byte>.Shared.Rent(510);
            try
            {
                while (await reader.WaitToReadAsync())
                {
                    if (reader.TryRead(out var result))
                    {
                        if (result.Item2 > 0)
                        {
                            using (var view = NetCache.Share.CreateAccessor(result.Item1, result.Item2))
                            {
                                int size = 0;

                                while ((size = await view.ReadAsync(buffer, 0, buffer.Length)) > 0)
                                {
                                    await socket.SendAsync(new ArraySegment<byte>(buffer, 0, size));
                                }
                                _ActiveTime = Utility.TimeSpan();
                            }
                        }
                        else
                        {
                            await socket.SendAsync(new ArraySegment<byte>(_ping));
                        }
                    }
                }
            }
            catch //(Exception ex)
            {
                this.Close();
            }
            finally
            {
                ArrayPool<byte>.Shared.Return(buffer);
            }
        }
        public int SendQty
        {
            get
            {
                return _sendQty;
            }
        }
        public int ReceiveQty
        {
            get
            {
                return _receiveQty;
            }
        }
        //    channel =
        Channel<Tuple<long, int>> channel = Channel.CreateUnbounded<Tuple<long, int>>(new UnboundedChannelOptions()
        {
            SingleReader = true,
            SingleWriter = false
        });
        public static bool ResponseHeader(byte[] data, int offset, int size, HeadValues headers, out HttpStatusCode m_StatusCode)
        {
            m_StatusCode = HttpStatusCode.InternalServerError;
            var start = offset;
            try
            {
                for (var ci = 0; ci < size - 2; ci++)
                {
                    var index = ci + offset;

                    if (data[index] == 10 && data[index - 1] == 13)
                    {
                        var heaerValue = new StringValue(data, start, index - start - 1);
                        var span = heaerValue.Span;
                        if (start == offset)
                        {
                            var l = span.IndexOf((byte)' ');
                            if (l > 0 && span.StartsWith("HTTP/"u8))
                            {
                                span = heaerValue.Slice(l + 1).Span;
                                var fhv = span.IndexOf((byte)' ');
                                if (fhv > 0)
                                {
                                    m_StatusCode = UMC.Data.Utility.Parse(span.Slice(0, fhv).UTF8(), HttpStatusCode.NotExtended);

                                }
                                else
                                {
                                    m_StatusCode = UMC.Data.Utility.Parse(span.UTF8(), HttpStatusCode.NotExtended);
                                }
                            }
                            else
                            {
                                return false;
                            }
                        }
                        else
                        {
                            var vi = span.IndexOf((byte)':');
                            var key = heaerValue.Slice(0, vi);
                            var value = heaerValue.Slice(vi + 2);
                            headers.Add(new HeadValue(key, value));
                        }

                        start = index + 1;
                    }
                }
                return true;
            }
            catch
            {
                return false;
            }
        }
        protected const byte STX = 0x02;
        protected Socket socket;
        private void Http(object sender, SocketAsyncEventArgs e)
        {
            switch (e.LastOperation)
            {
                case SocketAsyncOperation.Receive:
                    ProcessReceive(e);
                    break;
                case SocketAsyncOperation.Send:
                    break;
                default:
                    break;
            }
        }
        void Receive(SocketAsyncEventArgs e)
        {
            var size = e.Offset + e.BytesTransferred;
            var data = this.receiveData;
            int len = this.Receive(data, 0, size);
            _receiveQty += size;
            if (len > 0)
            {
                Array.Copy(data, size - len, data, 0, len);
                e.SetBuffer(data, len, data.Length - len);
            }
            else
            {
                e.SetBuffer(data);
            }
        }
        private void ProcessReceive(SocketAsyncEventArgs e)
        {
            if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
            {
                _ActiveTime = Utility.TimeSpan();
                this.Receive(e);

                try
                {
                    while (!socket.ReceiveAsync(e))
                    {
                        if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
                        {

                            this.Receive(e);
                        }
                        else
                        {
                            this.Close();
                            break;
                        }
                    }
                }
                catch //(Exception ex)
                {
                    this.Close();
                }
            }
            else
            {
                this.Close();

            }
        }
        int _receiveQty, _sendQty;
        byte[] receiveData = new byte[512];
        public virtual void Bridge(Socket socket)
        {
            SocketAsyncEventArgs eventArgs = new SocketAsyncEventArgs();

            eventArgs.SetBuffer(receiveData);
            eventArgs.Completed += Http;
            this._IsBridging = true;
            this.socket = socket;
            Receive(channel.Reader);
            if (!this.socket.ReceiveAsync(eventArgs))
            {
                ProcessReceive(eventArgs);
            }


        }
        bool _IsBridging;
        public bool IsBridging => _IsBridging;
        public virtual void Close()
        {
            _receiveQty = 0;
            _sendQty = 0;
            _IsBridging = false;
            try
            {
                channel.Writer.TryComplete();
                socket.Close();
                socket.Dispose();
            }
            catch
            {

            }
        }
        class Writer : DataWriter
        {
            public override void Prepare(ReadOnlySpan<byte> bytes)
            {
                this.Flush();
                ls[0] = 0x05;
                if (disposed == false)
                {
                    if (bytes.Length == 0)
                    {
                        Flush();
                        BitConverter.TryWriteBytes(ls.AsSpan(5), 0);
                        _netWrite(ls, 0, 9);
                    }
                    else
                    {
                        var max = 409;
                        while (size + bytes.Length >= max)
                        {
                            var len = max - size;
                            bytes.Slice(0, len).CopyTo(ls.AsSpan(size));

                            BitConverter.TryWriteBytes(ls.AsSpan(5), max - 9);
                            _netWrite(ls, 0, max);
                            bytes = bytes.Slice(len);
                            size = 9;
                        }
                        bytes.CopyTo(ls.AsSpan(size));
                        size += bytes.Length;
                    }
                }
                this.Flush();
            }
            byte[] ls = System.Buffers.ArrayPool<byte>.Shared.Rent(410);
            private NetWriteData _netWrite;
            public Writer(int pid, NetWriteData netWrite)
            {
                ls[0] = STX;
                BitConverter.TryWriteBytes(ls.AsSpan(1), pid);
                this._netWrite = netWrite;
            }
            int size = 9;
            public override void Write(ReadOnlySpan<byte> bytes)
            {
                ls[0] = STX;
                if (disposed == false)
                {
                    if (bytes.Length == 0)
                    {
                        Flush();
                        BitConverter.TryWriteBytes(ls.AsSpan(5), 0);
                        _netWrite(ls, 0, 9);
                    }
                    else
                    {
                        var max = 409;
                        while (size + bytes.Length >= max)
                        {
                            var len = max - size;
                            bytes.Slice(0, len).CopyTo(ls.AsSpan(size));

                            BitConverter.TryWriteBytes(ls.AsSpan(5), max - 9);
                            _netWrite(ls, 0, max);
                            bytes = bytes.Slice(len);
                            size = 9;
                        }
                        bytes.CopyTo(ls.AsSpan(size));
                        size += bytes.Length;
                    }
                }
            }
            public override void Flush()
            {
                if (size > 9)
                {
                    BitConverter.TryWriteBytes(ls.AsSpan(5), size - 9);
                    _netWrite(ls, 0, size);
                    size = 9;
                }
            }
            bool disposed = false;
            public override void Dispose()
            {
                if (disposed == false)
                {
                    disposed = true;
                    this.Flush();
                    if (ls != null)
                        System.Buffers.ArrayPool<byte>.Shared.Return(ls);
                    ls = null;
                    this._netWrite = null;
                }
            }
        }
        public DataWriter Write(int pid)
        {
            return new Writer(pid, this.Write);

        }
        uint _ActiveTime;
        public uint ActiveTime => _ActiveTime;//{ get; private set; }

        protected void Write(byte[] data, int offset, int count)
        {
            _sendQty += count;
            if (_IsBridging)
            {
                channel.Writer.TryWrite(new Tuple<long, int>(NetCache.Share.Append(data, offset, count), count));
            }

        }
        static readonly byte[] _ping = new byte[9] { 0x02, 0, 0, 0, 0, 0, 0, 0, 0 };
        public void Ping()
        {
            if (_IsBridging)
            {
                channel.Writer.TryWrite(new Tuple<long, int>(0, 0));
            }

        }
        public virtual void Write(int pid, byte[] data, int offset, int count)
        {
            var ls = System.Buffers.ArrayPool<byte>.Shared.Rent(410);
            ls[0] = STX;

            BitConverter.TryWriteBytes(ls.AsSpan(1), pid);

            try
            {
                if (count == 0)
                {
                    BitConverter.TryWriteBytes(ls.AsSpan(5), 0);
                    Write(ls, 0, 9);
                }
                else
                {
                    while (count > 0)
                    {
                        var max = 400;
                        int size = count > max ? max : count;
                        BitConverter.TryWriteBytes(ls.AsSpan(5), size);
                        Array.Copy(data, offset, ls, 9, size);
                        Write(ls, 0, size + 9);
                        count -= size;
                        offset += size;

                    }
                }
            }
            finally
            {
                System.Buffers.ArrayPool<byte>.Shared.Return(ls);
            }
        }

        protected virtual void Prepare(int pid, byte[] buffer, int index, int length)
        {

        }


        protected int Receive(byte[] buffer, int offset, int size)
        {

            while (size >= 9)
            {
                switch (buffer[offset])
                {
                    case 0x05:
                        {
                            var length = BitConverter.ToInt32(buffer, offset + 5);
                            if (length + 9 > size)
                            {
                                return size;
                            }
                            var pid = BitConverter.ToInt32(buffer, offset + 1);
                            offset += 9;
                            size -= 9;
                            Prepare(pid, buffer, offset, length);
                            size -= length;
                            offset += length;
                        }
                        break;
                    case STX:
                        {
                            var length = BitConverter.ToInt32(buffer, offset + 5);
                            if (length + 9 > size)
                            {
                                return size;
                            }
                            var pid = BitConverter.ToInt32(buffer, offset + 1);

                            offset += 9;
                            size -= 9;

                            Read(pid, buffer, offset, length);

                            size -= length;
                            offset += length;
                        }
                        break;
                    default:
                        this.Close();
                        return size;
                }
            }
            return size;
        }


        protected virtual void Read(int pid, byte[] buffer, int index, int length)
        {

        }
    }
}
